using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NHibernate;
using Rubikev.Common;
using Rubikev.Common.Data;
using ISession = NHibernate.ISession;

namespace Rubikev.NHibernate
{
   /// <summary>
   /// The NHibernate implementation of IDao
   /// </summary>
   /// <typeparam name="TEntity">The type of entity</typeparam>
   public class Dao<TEntity> : IDao<TEntity> where TEntity : class, IEntity
   {
      private readonly SessionAdapter session;

      public Dao(Common.Data.ISession session)
      {
         this.session = ((SessionAdapter)session);
      }

      private ISession NHibernateSession
      {
         get { return session.NHibernateSession; }
      }

      #region IDao<TEntity> Members

      /// <summary>
      /// Gets the entities matching the optional filter, or all entites if no filter is specified
      /// </summary>
      /// <param name="propertiesToFetch">The properties to eagerly load</param>
      /// <returns>The entities matching the filter</returns>
      public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] propertiesToFetch)
      {
         IQueryOver<TEntity, TEntity> ret = NHibernateSession.QueryOver<TEntity>();
         
         if (filter != null)
         {
            ret = ret.Where(filter);
         }

         if (propertiesToFetch != null)
         {
            ret = propertiesToFetch.Aggregate(ret,
                                              (current, requiredProperty) =>
                                              current.Fetch(requiredProperty).Eager);
         }

         return ret.Future().Distinct();
      }

      /// <summary>
      /// Adds the <paramref name="entity"/> if it is new, else updates it
      /// </summary>
      /// <param name="entity">The entity to add or update</param>
      /// <returns>The added or updated entity, following the operation</returns>
      public TEntity AddOrUpdate(TEntity entity)
      {
         if (entity == null)
         {
            throw new ArgumentNullException("entity", "Cannot save a null entity");
         }

         try
         {
            NHibernateSession.SaveOrUpdate(entity);
         }
         catch(PropertyValueException ex)
         {
            throw new InvalidEntityException(ex.Message, ex);
         }

         return entity;
      }

      /// <summary>
      /// Deletes the <paramref name="entity"/>
      /// </summary>
      /// <param name="entity">The entity to delete</param>
      public void Delete(TEntity entity)
      {
         if (entity == null)
         {
            throw new ArgumentNullException("entity", "Cannot delete a null entity");
         }

         NHibernateSession.Delete(entity);
      }

      /// <summary>
      /// Removes the entity from the session so that its changes are no longer tracked
      /// </summary>
      /// <param name="entity">The entity to evict</param>
      public void Evict(TEntity entity)
      {
         if (entity == null)
         {
            return;
         }

         NHibernateSession.Evict(entity);
      }

      /// <summary>
      /// The session used for the DAO
      /// </summary>
      public Common.Data.ISession Session
      {
         get { return session; }
      }

      #endregion
   }
}